
interface IReferenceInfo {

    /**引用路径 */
    url: string;

    /**引用次数 */
    count: number;

    /**最后引用时间 */
    lastTime: number;
}


interface IShowSpine {

    /**父节点,默认为舞台 */
    parent?: Laya.Sprite;

    /**节点名字 */
    name?: string;

    /**节点位置 */
    x?: number;

    /**节点位置 */
    y?: number;

    /**播放动作 */
    act?: string | number;

    /**播放声音 */
    sound?: string;

    /**是否清除旧的动画 default false */
    clearOldSpine?: boolean;

    /**使用缓存池 default: true */
    useCache?: boolean;

    /**播放完成自动清理,loop为true时无效 default: true */
    remove?: boolean;
}


interface ISpineOption {

    /**循环 */
    loop?: boolean;

    /**播放完成回调,不要在回调中操作本身，自动回收可能会错乱 */
    call?: Function;

    /**开始播放/加载完成 时回调 */
    onStart?: Function;

    /**相同动作，覆盖上一次 default: false */
    force?: boolean;

    /**动画播放起始时间 */
    start?: number;

    /**动画播放结束时间 */
    end?: number;

    /**缩放 */
    scale?: number;
}

export class SpineItem extends Laya.Sprite {

    private _spineRender: Laya.Spine2DRenderNode = null;

    private _act: string | number = null;
    private _option: ISpineOption = null;
    private _autoPause: boolean = true;
    private _active: boolean = false;

    constructor(public readonly spinePath: string) {
        super();
        this._active = true;
    }

    init(tempLet: Laya.SpineTemplet) {

        if (this.destroyed) {
            return;
        }

        if (this._spineRender && this._spineRender.source === this.spinePath) {
            this._spineRender.destroy();
            this._spineRender = null;
            warn("spine重复创建:", this.spinePath);
        }

        if (!this._spineRender) {
            this._spineRender = this.addComponent(Laya.Spine2DRenderNode);
        }

        this._spineRender.templet = tempLet;

        if (this._act === null || typeof this._act === "undefined") {
            return;
        }

        this.off(Laya.Event.DISPLAY, this, this.onAdd);
        this.off(Laya.Event.UNDISPLAY, this, this.onRemove);
        this.on(Laya.Event.DISPLAY, this, this.onAdd);
        this.on(Laya.Event.UNDISPLAY, this, this.onRemove);

        this.tryPlay(this._act, this._option);
    }

    tryPlay(act?: string | number, option?: ISpineOption) {
        if (!this._active || this.destroyed) {
            return;
        }

        act = act || 0;

        if (!this._spineRender) {
            this._act = act;
            this._option = option;
            return;
        }

        const { loop = true, force, start = 0, end = 0, scale = 1 } = option || {};

        this._spineRender.play(act, loop, force, start, end, false, false);
        this.event(Laya.Event.START);
        option.onStart && option.onStart();


        if (loop || !option.call) {
            return;
        }

        this.on(Laya.Event.STOPPED, this, () => {
            option.call && option.call();
            option.call = null;
        });
    }

    /**
     * removechild 自动隐藏，tryplay之前调用才生效
     * @param autoPause 
     */
    setAutoPause(autoPause: boolean) {
        this._autoPause = autoPause;
    }

    stop() {
        this._act = this._option = null;
        let item = this._spineRender;

        if (item && !this.destroyed) {
            item.stop();
            this.event(Laya.Event.STOPPED);
        }
    }

    stopByTime(time: number) {
        let item = this._spineRender;
        if (!item || this.destroyed) {
            return;
        }

        if (!this._spineRender) {
            return;
        }

        item.stop();
        this._spineRender.currentTime = time;
    }

    paused() {
        if (!this._active || this.destroyed) {
            return;
        }
        let item = this._spineRender;

        if (item) {
            log("暂停播放=======>", this.spinePath);
            item.paused();
        }
        this._act = this._option = null;
    }

    resume() {
        if (!this._active || this.destroyed) {
            return;
        }

        let item = this._spineRender;

        if (item) {
            log("恢复播放=======>", this.spinePath);
            item.resume()
        }
    }

    destroy() {
        if (this.destroyed) {
            return;
        }
        log("destroy sk=======>", this.spinePath);

        this.event(Laya.Event.END);
        let item = this._spineRender;

        this.offAll();
        this._spineRender = null;
        this._act = null;
        this._option = null;
        this._active = false;
        super.destroy(true);
    }

    clear() {
        this.destroy();
    }

    setActive(active: boolean) {
        this._active = active;

        if (!active) {
            this.stop();
            this.removeSelf();
        }
    }

    get active() {
        return this._active;
    }

    private onRemove() {
        this._autoPause && this.paused();
    }

    private onAdd() {
        this._autoPause && this.resume();
    }

}


class Spinemanage {

    /**spine资源 */
    private spMap = new Map<string, Laya.SpineTemplet | Promise<Laya.SpineTemplet>>();

    /**spine引用信息，释放资源时使用 */
    private spRefereInfo: Map<string, IReferenceInfo> = new Map<string, IReferenceInfo>();

    /**动画缓存池 */
    private spCacheList: Map<string, Set<SpineItem>> = new Map<string, Set<SpineItem>>();

    /**动画缓存池容量 */
    private readonly poolSize: number = 5;

    /**上次清理时间 */
    private oldDisposeTime: number = 0;

    constructor() {
        this.oldDisposeTime = Date.now();
    }

    /**
     * 加载动画
     * @param url 动画路径（全路径），基于后缀区分动画版本
     * @param version 
     * @returns 
     */
    public loadSpine(url: string): Promise<Laya.SpineTemplet> {

        if (!url.endsWith(".skel") && !url.endsWith(".json")) {
            return Promise.reject("不支持的动画格式:" + url);
        }

        let cached = this.spMap.get(url);

        if (cached) {
            if (cached instanceof Promise) {
                return cached;
            }
            if (!cached.destroyed) {
                return Promise.resolve(cached);
            }
        }

        let pms = Laya.loader.load(url).then(res => {
            if (!res) {
                return Promise.reject("动画加载失败:" + url);
            }

            this.spMap.set(url, res);
            return res;
        });

        this.spMap.set(url, pms);
        return pms;
    }



    /**
     * 创建动画
     * @param url 动画路径(全路径带后缀)
     * @returns 
     */
    createSk(url: string, useCache?: boolean): SpineItem {

        if (useCache) {
            let sp = this.getCacheSk(url);
            if (sp) {
                this.updateReferenceInfo(url, false);
                return sp;
            }
        }

        log("创建动画=======>", url);
        let sp = new SpineItem(url);

        this.loadSpine(url).then(res => {
            this.updateReferenceInfo(url, true);
            sp.init(res);
        }).catch(err => {
            console.error(err);
            sp.destroy();

        })

        return sp;
    }


    /**
     * 显示动画
     * @param url 动画路径(全路径带后缀),自动识别是否是3.8的版本
     * @param option 
     * @returns SpineItem
     */
    showAnim(url: string, option?: IShowSpine & ISpineOption): SpineItem {

        option = option || {};
        const parent = option.parent || Laya.stage;
        const name = option.name || "spine_node";
        const useCache = option.useCache !== false;
        const remove = option.remove !== false;
        const clearOldSpine = !!option.clearOldSpine;
        const x = option.x || 0;
        const y = option.y || 0;

        let sp = parent.getChildByName(name) as SpineItem;

        if (!sp) {
            sp = this.createSk(url, useCache);

        } else if (!(sp instanceof SpineItem)) {
            //有其它同名节点
            throw "repetition node:" + url;

        } else if (clearOldSpine) {
            //清理旧spine
            this.removeSpine(sp);
            sp = null;

        } else if (sp.spinePath !== url) {
            //spine改变
            log("spine url change: \n", sp.spinePath + "====>" + url);
            this.removeSpine(sp);
            sp = null;

        }

        if (!sp) {
            sp = this.createSk(url, useCache);

        }

        if (remove) {
            let call = option.call;
            option.call = () => {
                this.removeSpine(sp);
                call && call();
            }
        }

        parent.addChild(sp);
        sp.name = name;
        sp.pos(x, y);
        sp.setActive(true);
        sp.tryPlay(option.act, option);

        return sp;
    }

    /**
     * 清理动画
     * @param parent 
     * @param name 
     * @returns 
     */
    clearAni(parent: Laya.Sprite, name?: string) {
        if (!parent || parent.destroyed) return;

        name = name || "spine_node";
        let sp = parent.getChildByName(name) as SpineItem;
        if (!sp) {
            return;
        }

        if (sp instanceof SpineItem) {
            // sp.clear();
            this.removeSpine(sp);
            return;
        }
    }

    /**
     * 清理缓存池
     * @param url 不传全清
     */
    clearCache(url?: string) {
        if (!url) {
            this.spCacheList.forEach(list => {
                if (!list) {
                    return;
                }

                list.forEach(v => v.clear());
            })
            this.spCacheList.clear();
            log("清理缓存池=======>", "全部");
            return;
        }

        let list = this.spCacheList.get(url);
        if (list) {
            log("清理缓存池=======>", url);
            list.forEach(sp => {
                sp.clear();
            })
            this.spCacheList.delete(url);
        }
    }


    /**
     * 销毁未引用资源
     */
    disposeUnreferenced() {
        this.spMap.forEach((v, k) => {
            if (v instanceof Promise) {
                return;
            }

            if (v.referenceCount <= 0) {
                v.destroy();
            }
        })
    }

    /**
     * 销毁所有动效资源
     */
    disposeAll() {
        this.spCacheList.forEach(list => {
            if (list) {
                list.forEach(v => v.destroy());
            }
        });
        this.spCacheList.clear();

        this.spMap.forEach((v, k) => {
            if (v instanceof Promise) {
                return;
            }
            v.destroy();
        })


        this.spMap.clear();
    }


    /**移除并缓存动画 */
    removeSpine(sp: SpineItem) {

        let list = this.spCacheList.get(sp.spinePath);
        if (!list) {
            list = new Set();
            this.spCacheList.set(sp.spinePath, list);
        } else if (list.size >= this.poolSize) {
            sp.clear();
            return;
        }
        log("removeSpine:", sp.spinePath);
        sp.setActive(false);
        list.add(sp);
    }


    updateTime() {

        if (this.spMap.size <= 0) return;

        let now = Date.now();
        if (!this.oldDisposeTime) {
            this.oldDisposeTime = now;
        }

        //30秒内仅处理一次
        if (now - this.oldDisposeTime < 30000) {
            return;
        }

        let deleatKeys: string[] = [];
        this.spRefereInfo.forEach((v, k) => {

            //一分钟内使用过不处理
            if (now - v.lastTime < 60000) {
                return;
            }
            this.clearCache(k);

            let item = this.spMap.get(k);
            if (!item || item instanceof Promise) {
                return;
            }

            if (item.referenceCount > 0) {
                return;
            }

            deleatKeys.push(k);
            item.destroy();
            this.spMap.delete(k);
        });

        if (deleatKeys.length > 0) {
            log("disposeUnreferenced", deleatKeys);
            deleatKeys.forEach(k => this.spRefereInfo.delete(k));
        }

        this.oldDisposeTime = now;
    }

    private getCacheSk(url: string) {
        let list = this.spCacheList.get(url);
        if (!list) {
            return null;
        }

        while (list.size > 0) {
            let sp: SpineItem = list.values().next().value;
            list.delete(sp);
            if (sp.destroyed || sp.active) {
                continue;
            }
            return sp;
        }

        return null;
    }

    private updateReferenceInfo(url: string, add: boolean) {
        let info = this.spRefereInfo.get(url);
        let now = Date.now();
        if (!info) {
            info = { url: url, count: add ? 1 : 0, lastTime: now };
            this.spRefereInfo.set(url, info);
        } else {
            info.count += add ? 1 : 0;
            info.lastTime = now;
        }
    }


}

function log(...str: any[]) {
    console.log("[--Spine--]", ...str);
}

function warn(...str: any[]) {
    console.warn("[--Spine--]", ...str);
}

export const spm = new Spinemanage();